import { CreationAttributes, Op } from 'sequelize'
import { TokenType } from '~/constants/TokenType'
import { Token, redis } from '~/db'
import { singleton } from '~/libs/singleton'

export namespace TokenManager {
    function getCacheKey(type: TokenType, app_id: string, component_app_id: string): string {
        return `token:${app_id}:${component_app_id}:${type}`
    }

    export function get(type: TokenType, app_id: string, component_app_id = '') {
        const key = getCacheKey(type, app_id, component_app_id)
        return singleton(`get:${key}`, async () => {
            //先尝试通过redis获取
            const cache = await redis.get(key)
            if (cache) {
                return cache
            }

            //尝试从数据库获取
            const row = await Token.findOne({
                where: {
                    [Op.and]: [
                        {
                            type,
                            app_id,
                            component_app_id,
                        },
                        {
                            [Op.or]: [
                                {
                                    expire_time: {
                                        [Op.gt]: new Date(),
                                    },
                                },
                                {
                                    expire_time: null,
                                },
                            ],
                        },
                    ],
                },
                attributes: ['token', 'expire_time'],
            })

            if (row) {
                //写入缓存
                if (row.expire_time) {
                    await redis.set(key, row.token, 'PX', row.expire_time.valueOf() - Date.now())
                } else {
                    await redis.set(key, row.token)
                }
                return row.token
            }
        })
    }

    export function getWithExpires(type: TokenType, app_id: string, component_app_id = '') {
        const key = getCacheKey(type, app_id, component_app_id)

        return singleton(`get:${key}`, async () => {
            //先尝试通过redis获取
            const cache = await redis.get(key)
            if (cache) {
                return {
                    token: cache,
                    expires: await redis.ttl(key),
                }
            }

            //尝试从数据库获取
            const row = await Token.findOne({
                where: {
                    [Op.and]: [
                        {
                            type,
                            app_id,
                            component_app_id,
                        },
                        {
                            [Op.or]: [
                                {
                                    expire_time: {
                                        [Op.gt]: new Date(),
                                    },
                                },
                                {
                                    expire_time: null,
                                },
                            ],
                        },
                    ],
                },
                attributes: ['token', 'expire_time'],
            })

            if (row) {
                //写入缓存
                if (row.expire_time) {
                    await redis.set(key, row.token, 'PX', row.expire_time.valueOf() - Date.now())
                    return {
                        token: row.token,
                        expires: Math.floor((row.expire_time.valueOf() - Date.now()) / 1000),
                    }
                } else {
                    await redis.set(key, row.token)
                    return {
                        token: row.token,
                        expires: 0,
                    }
                }
            }
        })
    }

    export async function set(data: CreationAttributes<Token>) {
        const key = getCacheKey(data.type, data.app_id, data.component_app_id ?? '')
        await Token.upsert(data)
        if (data.expire_time) {
            await redis.set(key, data.token, 'PX', data.expire_time.valueOf() - Date.now())
        } else {
            await redis.set(key, data.token)
        }
    }

    export async function clean(app_id: string, component_app_id: string) {
        const keys = await redis.keys(getCacheKey('*' as any, app_id, component_app_id))
        if (keys.length > 0) {
            await redis.del(...keys)
        }
    }
}
